<template>
  <scroll-view :id="domId" class="scroll-view_hold" :scroll-x="true" :scroll-with-animation="true"
               :scroll-left="position"
  >
    <view class="tabs__container" :style="{ justifyContent: center ? 'center' : 'flex-start', ...getWrapStyle }">
      <view v-for="(item, index) in list" :key="`tab-item${index}`" :style="{ flex: center ? 1 : 0, ...itemWrapStyle }"
            class="scroll-view-item" @click="nodeClick(index, item)"
      >
        <view v-if="current === index" class="hidden-default-comp" :style="getItemDefaultStyle">
          {{ defaultText }}
        </view>
        <view class="hidden-active-comp" :style="getActiveStyle">
          <slot :item="item">
            {{ fields ? limitText(item[fields]) : limitText(item) }}
          </slot>
        </view>
        <view :style="current === index ? getActiveStyle : getItemDefaultStyle">
          <slot :item="item">
            {{ fields ? limitText(item[fields]) : limitText(item) }}
          </slot>
        </view>
      </view>
      <view v-if="showLine" class="tab_bottom_line" :class="[{ animation: startAnimation && lineAnimation }]"
            :style="getUnderlineStyle"
      />
    </view>
  </scroll-view>
</template>

<script>
function getDomId() {
  const len = 16
  const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' /** **默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1 */
  const maxPos = $chars.length
  let pwd = ''
  for (let i = 0; i < len; i++) {
    pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
  }
  return `xfjpeter_${pwd}`
}
function areFloatsEqual(a, b, epsilon = 0.0001) {
  return Math.abs(a - b) < epsilon
}
export default {
  props: {
    list: {
      type: Array,
      required: true,
      default: () => ([])
    },
    center: {
      // 居中
      type: Boolean,
      default: false
    },
    wrapStyle: {
      type: Object,
      default: () => ({})
    },
    itemWrapStyle: {
      type: Object,
      default: () => ({ padding: '0' })
    },
    itemDefaultStyle: {
      type: Object,
      default: () => ({})
    },
    color: {
      type: String,
      default: '#818181'
    },
    itemActiveStyle: {
      type: Object,
      default: () => ({})
    },
    showLine: {
      type: Boolean,
      default: false
    },
    underlineStyle: {
      type: Object,
      default: () => ({})
    },
    lineScale: {
      type: [Number, String],
      default: 0.8
    },
    lineColor: {
      type: String,
      default: '#ff7c7a'
    },
    lineAnimation: {
      type: Boolean,
      default: true
    },
    // 名字过长是否缩写
    isEllipsis: {
      type: Boolean,
      default: false
    },
    // 超过数量
    ellipsisNums: {
      type: [Number, String],
      default: 5
    },
    fields: {
      type: String,
      default: ''
    },
    modelValue: {
      type: Number,
      default: 0
    },
    windowWidth: {
      type: Number,
      default: 750
    }
  },
  emits: ['update:modelValue', 'change'],
  data() {
    return {
      defaultText: '',
      scrollWithAnimation: true,

      lineWidth: 0,
      lineLeft: 0,
      startAnimation: false, // 是否开启动画

      domId: getDomId(),
      tabsRect: {},
      current: 0,
      scrollViewRect: [], // 存储scroll信息
      position: 0 // 滑动位置
    }
  },
  computed: {
    getWrapStyle() {
      return Object.assign({ padding: '8px 0' }, this.wrapStyle)
    },
    getItemDefaultStyle() {
      return Object.assign({ color: this.color, padding: '8px 14px', transition: 'all .2s' }, this.center ? { 'flex': 1, 'text-align': 'center' } : {}, this.itemDefaultStyle)
    },
    getUnderlineStyle() {
      return {
        ...Object.assign({ height: '2px', background: this.lineColor, borderRadius: '8px' }, this.underlineStyle),
        width: `${this.lineWidth}px`,
        transform: `translate3d(${this.lineLeft}px, 0, 0)`
      }
    },
    getActiveStyle() {
      return Object.assign(Object.assign({}, this.getItemDefaultStyle, { color: '#000' }), this.itemActiveStyle)
    },
    scrollWidth() {
      return this.scrollViewRect.reduce((a, b) => a + b.width, 0)
    }
  },
  watch: {
    modelValue: {
      immediate: true,
      handler(newVal, oldVal) {
        if (typeof newVal === 'number') {
          this.current = newVal
        }
      }
    },
    list: {
      handler(nVal) {
        if (!nVal || nVal.length === 0) return
        this.comDefaultText()
        setTimeout(() => {
          this.getListWrapPos(() => {
            this.getListItemScrollPos(() => {
              this.calcPos(this.current, false)
            })
          })
        }, 350) // web测试最少180，多加一些，防止bug
      },
      deep: true,
      immediate: true
    },
    current: {
      immediate: true,
      handler(newVal, oldVal) {
        if (typeof newVal === 'number') {
          if (this.list.length === 0) return
          this.getScrollViewRect(newVal)
          this.calcPos(newVal, typeof oldVal !== 'undefined')
        }
      }
    },
  },
  methods: {
    getScrollViewRect(current) {
      if (!this.scrollViewRect[current]) return

      let temp = 0
      for (let i = 0; i < this.scrollViewRect.length; i++) {
        const item = this.scrollViewRect[i]
        item.width = item.defaultWidth
        if (i === current) {
          item.width = item.activeWidth
        }
        if (i !== 0) {
          temp += (this.scrollViewRect[i - 1]).width
        }
        item.left = temp - this.position
      }
    },
    comDefaultText() { // 测量选中的文字的初始样式宽度
      if (!this.list[this.current]) return
      this.defaultText = this.fields ? this.limitText(this.list[this.current][this.fields]) : this.limitText(this.list[this.current])
    },
    limitText(val) {
      val += ''
      if (!val) return
      if (!this.isEllipsis) return val
      return val.length > this.ellipsisNums ? `${val.slice(0, this.ellipsisNums)}...` : val
    },
    calcPos(choseInd, animation = true) {
      this.startAnimation = animation // 解决初始滑动动画
      const hasitem = this.scrollViewRect[choseInd]

      if (!hasitem) return
      // 当前活动tab的布局信息，有tab菜单的width和left(为元素左边界到父元素左边界的距离)等信息
      // 累加得到当前item到左边的距离
      const offsetLeft = this.scrollViewRect
        .slice(0, choseInd)
        .reduce((total, curr) => total + curr.width, 0)
      // #ifdef MP-WEIXIN
      const windowWidth = uni.upx2px(this.windowWidth)
      // #endif
      // #ifndef MP-WEIXIN
      const windowWidth = uni.rpx2px(this.windowWidth)
      // #endif
      // 将活动的tabs-item移动到屏幕正中间，实际上是对scroll-view的移动
      let scrollLeft = offsetLeft - (this.tabsRect.width - hasitem.width) / 2 - (windowWidth - this.tabsRect
        .right) / 2 + this.tabsRect.left / 2 + hasitem.left
      // 这里做一个限制，限制scrollLeft的最大值为整个scroll-view宽度减去tabs组件的宽度
      scrollLeft = Math.min(scrollLeft, this.scrollWidth - this.tabsRect.width)
      this.position = Math.max(0, scrollLeft)

      this.setLine(hasitem, offsetLeft)
    },
    setLine({ width, activeWidth }, offsetLeft) {
      if (!this.showLine) return
      this.lineWidth = width * this.lineScale
      this.lineLeft = offsetLeft + (width - this.lineWidth) / 2 // - this.tabsRect.left / 2
    },
    createQueryHandler() {
      const query = uni
        .createSelectorQuery()
        // #ifndef MP-ALIPAY
        .in(this)
      // #endif
      return query
    },
    getListWrapPos(cb) {
      // 获取列表宽度
      const query = this.createQueryHandler()
      query.select(`#${this.domId}`).boundingClientRect(data => {
        this.tabsRect = data
        cb && cb()
      })
      query.exec()
    },
    getListItemScrollPos(cb) {
      const query = this.createQueryHandler()
      // 获取列表每个dom的偏移量
      query.selectAll(`#${this.domId} .scroll-view-item`).boundingClientRect((res) => {
        if (!res) return

        this.scrollViewRect = res.map(e => ({ left: e.left, width: e.width, defaultWidth: e.width, activeWidth: 0 }))
      })
      // 获取列表每个dom的偏移量
      query.selectAll(`#${this.domId} .hidden-active-comp`).boundingClientRect((res) => {
        if (!res) return

        this.scrollViewRect.forEach((item, index) => {
          item.activeWidth = res[index].width || 0
        })
      })
      query.selectAll(`#${this.domId} .hidden-default-comp`).boundingClientRect((res) => {
        if (!res) return
        if (this.scrollViewRect[this.current]) {
          this.scrollViewRect[this.current].defaultWidth = res[0]?.width
        }
        cb && cb()
      })
      query.exec()
    },
    nodeClick(index, item) {
      this.current = index
      this.$emit('update:modelValue', index)
      this.$emit('change', item, index)
    }
  }
}
</script>

<style>
.scroll-view_hold {
  position: relative;
  width: 100%;
  overflow: hidden;
  white-space: nowrap;
}

.scroll-view_hold ::-webkit-scrollbar {
  width: 0;
  height: 0;
  color: transparent;
}

.tabs__container {
  /* position: relative;
  display: flex;
  white-space: nowrap; */
  position: relative;
  flex-direction: row;
  display: flex;
  align-items: center;
}

.scroll-view-item {
  position: relative;
  flex: 1;
  white-space: nowrap;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 99;
}

.hidden-active-comp,
.hidden-default-comp {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: 100%;
  opacity: 0;
}

/* .hidden-active-comp {
} */

.tab_bottom_line {
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: 0;
}

.tab_bottom_line.animation {
  transition: all 0.3s linear;
}
</style>
